package com.dayangxiaoen.pms.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dayangxiaoen.pms.dto.UserDTO;
import com.dayangxiaoen.pms.enums.StatusCode;
import com.dayangxiaoen.pms.mapper.UserMapper;
import com.dayangxiaoen.pms.po.User;
import com.dayangxiaoen.pms.service.UserService;
import com.dayangxiaoen.pms.utils.JWTUtils;
import com.dayangxiaoen.pms.vo.Result;
import com.dayangxiaoen.pms.vo.UserVO;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;


import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.dayangxiaoen.pms.utils.RedisConstants.*;

/**
 * @author dayangxiaoen
 *
 * 用户登录的相关操作
 *
 * 包括   登录          login()
 * 校验   token       checkToken()
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    //加密盐用于加密
    private static final String slat = "666#dayangxiaoen#666";


    @Override
    public Result login(UserDTO userDTO) {


        // TODO 1 检查参数是否合法
        // 获取用户名
        String userName = userDTO.getUserName();
        // 获取用户密码
        String password = userDTO.getPassword();
        // 判断用户名或密码是否为空
        // 正常情况下应该不会为空，前端会对空值做出空值
        // 这里做出判断保持完整性
        if(StringUtils.isBlank(userName) || StringUtils.isBlank(password)){
            return Result.fail(StatusCode.USERNAME_OR_PASSWORD_NULL);
        }
        // 将获取到的用户密码 用加密盐进行加密
        password = DigestUtils.md5Hex(password + slat);


        String key = LOGIN_USERNAME + userDTO.getUserName();
        String json = redisTemplate.opsForValue().get(key);
        String errorNumber = redisTemplate.opsForValue().get(LOGIN_LIMIT_KEY + userDTO.getUserName());
        if (errorNumber!=null&&Integer.parseInt(errorNumber)>5) {
            //密码错误五次，限制登陆
            return Result.fail(StatusCode.LOGIN_LIMIT_ERROR);
        }
        if (StrUtil.isBlank(json) && json != null) {
            //json值isBlank且不等于null,说明是为解决缓存穿透而设置的空值（isBlank:json为空或者等于null）
            return Result.fail( StatusCode.USER_NOT_REGISTER);
        }



        // TODO 2. 根据获取到的用户名 和 加密后的密码 在数据库中查找是否能找到
        User user = lambdaQuery()
                .eq(User::getUserName, userName)
                .eq(User::getPassword, password)
                .one();
        // 判断用户是否为空
        if(user == null){
            // 如果为空 则用户名或密码错误
            // 针对该用户名和密码的错误次数 + 1
            redisTemplate.opsForValue().increment(key);
            // 每一次登录失败都会设置一次倒计时
            redisTemplate.expire(LOGIN_LIMIT_TTL+userDTO.getUserName(),LOGIN_LIMIT_TTL,TimeUnit.SECONDS);
            // 用户名或密码错误
            return Result.fail(StatusCode.USER_NOTFOUND);
        }


        // 如果登录成功 则重置相关的 错误次数 key
        redisTemplate.opsForValue().set(key,"0");

        // TODO 3. 存在用户 生成相关token 封装相关VO
        String token = JWTUtils.createToken(user.getId());


        // TODO 4. token 放入redis
        //  redis 中 存放相关 token user 信息 并 设置过其实时间 为一天
        redisTemplate.opsForValue().set("TOKEN_" + token, JSON.toJSONString(user),1, TimeUnit.DAYS);

        // TODO 5. 将用户封装到 VO
        UserVO userVo = new UserVO();
//        BeanUtils.copyProperties(userVo,user);
        // 作者手动 copy
        userVo.setUserName(user.getUserName());
        userVo.setRole(user.getRole());

        // TODO 6. 返回结果
        return  Result.sucess(userVo,token);
    }

    @Override
    public User checkToken(String token) {
        // TODO 1. 判断 token 是否为空 同时 判断 token 是否正确
        if(StringUtils.isBlank(token)){
            return null;
        }
        // 获得 键值对
        Map<String,Object> stringObjectMap = JWTUtils.checkToken(token);
        // 如果键值对为空 返回 NULL
        if(stringObjectMap == null){
            return null;
        }

        // TODO 2. 根据传过来的 token 在Redis 中 获得相关的用户字符串
        String userJson = redisTemplate.opsForValue().get("TOKEN_" + token);
        // 判断字符串是否为空
        if(StringUtils.isBlank(userJson)){
            return null;
        }

        // TODO 3.将活的到的 JSON 字符串转回 User 类型
        User user = JSON.parseObject(userJson, User.class);
        return user;
    }



}
